package com.luych.toolbox.data.service.service;

import com.luych.toolbox.common.enums.DataAgentStatus;
import com.luych.toolbox.common.enums.DataSourceType;
import com.luych.toolbox.common.feign.intf.base.service.UserFeign;
import com.luych.toolbox.common.feign.param.data.service.DataAgentParam;
import com.luych.toolbox.common.feign.view.base.service.UserView;
import com.luych.toolbox.common.feign.view.data.service.DataAgentView;
import com.luych.toolbox.common.page.PageParam;
import com.luych.toolbox.common.page.PageResult;
import com.luych.toolbox.common.service.base.BaseConvert;
import com.luych.toolbox.common.service.base.BaseService;
import com.luych.toolbox.common.service.exception.HttpException;
import com.luych.toolbox.common.service.page.SpringPageParamAdapter;
import com.luych.toolbox.common.service.page.SpringPageResultAdapter;
import com.luych.toolbox.data.service.constant.Constant;
import com.luych.toolbox.data.service.dao.DataAgentDao;
import com.luych.toolbox.data.service.dao.DataBackupHistoryDao;
import com.luych.toolbox.data.service.dao.DataBackupPlanDao;
import com.luych.toolbox.data.service.dao.DataUpgradeDetailDao;
import com.luych.toolbox.data.service.entity.DataAgent;
import com.luych.toolbox.data.service.job.DataAgentHealthCheckJob;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
public class DataAgentService
        extends BaseService<DataAgentView, DataAgentParam, DataAgent>
        implements ApplicationListener<ApplicationReadyEvent> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DataAgentService.class);

    private static final BaseConvert<DataAgentParam, DataAgent> dataAgentParamToEntity = DataAgent::new;
    private static final BaseConvert<DataAgent, DataAgentView> dataAgentEntityToView = DataAgentView::new;

    private final Boolean rescheduleWhenStart;
    private final Scheduler scheduler;
    private final DataBackupHistoryDao dataBackupHistoryDao;
    private final DataBackupPlanDao dataBackupPlanDao;
    private final DataUpgradeDetailDao dataUpgradeDetailDao;
    private final DataAgentDao dataAgentDao;
    private final UserFeign userFeign;

    @Autowired
    public DataAgentService(@Value("${spring.quartz.rescheduleWhenStart:false}") Boolean rescheduleWhenStart,
                            Scheduler scheduler,
                            DataBackupHistoryDao dataBackupHistoryDao,
                            DataBackupPlanDao dataBackupPlanDao,
                            DataUpgradeDetailDao dataUpgradeDetailDao,
                            DataAgentDao dataAgentDao,
                            UserFeign userFeign) {
        super(dataAgentDao, dataAgentParamToEntity, dataAgentEntityToView);
        this.rescheduleWhenStart = rescheduleWhenStart;
        this.scheduler = scheduler;
        this.dataBackupHistoryDao = dataBackupHistoryDao;
        this.dataBackupPlanDao = dataBackupPlanDao;
        this.dataUpgradeDetailDao = dataUpgradeDetailDao;
        this.dataAgentDao = dataAgentDao;
        this.userFeign = userFeign;
    }

    @Override
    protected DataAgentView fill(DataAgentView view) {
        dataAgentDao.findById(view.getId()).map(DataAgent::getOwnerId).map(userFeign::get).ifPresent(view::setOwner);
        return view;
    }

    @Override
    public DataAgentView save(DataAgentParam param) {
        // check owner
        UserView owner = userFeign.get(param.getOwnerId());
        if (owner == null) {
            throw new HttpException(HttpStatus.BAD_REQUEST, Constant.DATA_SOURCE_SAVE_OWNER_ERROR);
        }
        // save dataagent
        DataAgent dataAgentParam = dataAgentParamToEntity.apply(param);
        dataAgentParam.setStatus(DataAgentStatus.WAITING);
        DataAgent dataAgentResult = dataAgentDao.save(dataAgentParam);
        return this.fill(dataAgentEntityToView.apply(dataAgentResult));
    }

    private JobKey getJobKey(String id) {
        return new JobKey(id, DataAgentHealthCheckJob.class.getName());
    }

    private JobDetail getJobDetail(String id) {
        Map<String, Object> jobParam = new HashMap<String, Object>() {{
            this.put("id", id);
        }};
        return JobBuilder.newJob(DataAgentHealthCheckJob.class)
                .withIdentity(this.getJobKey(id))
                .usingJobData(new JobDataMap(jobParam))
                .build();
    }

    private Trigger getJobTrigger(String id) {
        return TriggerBuilder.newTrigger()
                .withIdentity(id, DataAgentHealthCheckJob.class.getName())
                .withSchedule(SimpleScheduleBuilder.repeatMinutelyForever(10))
                .build();
    }

    @Override
    public DataAgentView create(DataAgentParam param) {
        Assert.notNull(param.getName(), "Param name can't be empty");
        Assert.notNull(param.getType(), "Param type can't be empty");
        Assert.notNull(param.getHost(), "Param host can't be empty");
        Assert.notNull(param.getPort(), "Param port can't be empty");
        Assert.notNull(param.getUser(), "Param user can't be empty");
        Assert.notNull(param.getPass(), "Param pass can't be empty");
        Assert.notNull(param.getPath(), "Param path can't be empty");
        Assert.notNull(param.getOwnerId(), "Param ownerId can't be empty");

        // check name exists
        if (dataAgentDao.findByName(param.getName()).isPresent()) {
            throw new HttpException(HttpStatus.CONFLICT, Constant.DATA_AGENT_SAVE_NAME_USED);
        }

        // save
        DataAgentView view = this.save(param);

        // schedule
        try {
            scheduler.scheduleJob(this.getJobDetail(view.getId()), this.getJobTrigger(view.getId()));
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }

        // return
        return view;
    }

    @Override
    public DataAgentView modify(DataAgentParam param) {
        Assert.notNull(param.getId(), "Param id can't be empty");
        Assert.notNull(param.getType(), "Param type can't be empty");
        Assert.notNull(param.getHost(), "Param host can't be empty");
        Assert.notNull(param.getPort(), "Param port can't be empty");
        Assert.notNull(param.getUser(), "Param user can't be empty");
        Assert.notNull(param.getPass(), "Param pass can't be empty");
        Assert.notNull(param.getPath(), "Param path can't be empty");

        // check name exists
        Optional<DataAgent> dataAgentOptOfName = dataAgentDao.findByName(param.getName());
        if (dataAgentOptOfName.isPresent() && !dataAgentOptOfName.get().getId().equals(param.getId())) {
            throw new HttpException(HttpStatus.CONFLICT, Constant.DATA_SOURCE_SAVE_NAME_USED);
        }

        // save
        DataAgentView view = this.save(param);

        // schedule
        try {
            scheduler.deleteJob(this.getJobKey(view.getId()));
            scheduler.scheduleJob(this.getJobDetail(view.getId()), this.getJobTrigger(view.getId()));
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }

        // return
        return view;
    }

    @Override
    public void remove(String id) {
        int used = 0;
        used += dataBackupPlanDao.countByDataAgentId(id);
        used += dataBackupHistoryDao.countByDataAgentId(id);
        used += dataUpgradeDetailDao.countByDataAgentId(id);
        if (used > 0) {
            throw new HttpException(HttpStatus.FORBIDDEN, Constant.DATA_AGENT_DELETE_REJECT_USED);
        }

        // schedule
        try {
            scheduler.deleteJob(this.getJobKey(id));
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }

        // delete
        dataAgentDao.deleteById(id);
    }

    public PageResult<DataAgentView> pageByKeyword(PageParam pageParam, String keyword) {
        Page<DataAgent> page;
        if (StringUtils.isEmpty(keyword)) {
            page = dataAgentDao.findAll(new SpringPageParamAdapter(pageParam));
        } else {
            page = dataAgentDao.findByNameLikeOrHostLike('%' + keyword + '%', '%' + keyword + '%', new SpringPageParamAdapter(pageParam));
        }
        PageResult<DataAgent> pageResult = new SpringPageResultAdapter<>(page);
        return pageResult.convert(dataAgentEntityToView).convert(this::fill);
    }

    public PageResult<DataAgentView> pageByType(PageParam pageParam, DataSourceType type) {
        Page<DataAgent> page;
        if (type == null) {
            page = dataAgentDao.findAll(new SpringPageParamAdapter(pageParam));
        } else {
            page = dataAgentDao.findByType(type, new SpringPageParamAdapter(pageParam));
        }
        PageResult<DataAgent> pageResult = new SpringPageResultAdapter<>(page);
        return pageResult.convert(dataAgentEntityToView).convert(this::fill);
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        if (Boolean.TRUE.equals(rescheduleWhenStart)) {
            List<DataAgent> dataAgents = dataAgentDao.findAll();
            for (DataAgent dataAgent : dataAgents) {
                try {
                    scheduler.deleteJob(this.getJobKey(dataAgent.getId()));
                    scheduler.scheduleJob(this.getJobDetail(dataAgent.getId()), this.getJobTrigger(dataAgent.getId()));
                } catch (SchedulerException e) {
                    throw new IllegalStateException(e);
                }
            }
        }
    }
}
